<?php
/*
     Anacondha - a PHP class to get books from Amazon!
     Copyright (C) 2010 Julio Montoya Armas <gugli100@gmail.com>
     Version  0.1 
     http://code.google.com/p/anacondha
     
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation, either version 1,2,3 of the License, or
     (at your option) any later version.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.     
*/

/* Declare some script wide constants */
define('AMAZON_ECS_SCHEMA', '2009-03-31');

 /* Anacondha class definition */
require 'configuration.php';

class Anacondha {	 
 	
}

/**
 * Perform an HTTP request.
 *
 * This is a flexible and powerful HTTP client implementation. Correctly handles
 * GET, POST, PUT or any other HTTP requests. Handles redirects.
 *
 * @param $url
 *   A string containing a fully qualified URI.
 * @param $headers
 *   An array containing an HTTP header => value pair.
 * @param $method
 *   A string defining the HTTP request to use.
 * @param $data
 *   A string containing data to include in the request.
 * @param $retry
 *   An integer representing how many times to retry the request in case of a
 *   redirect.
 * @return
 *   An object containing the HTTP request headers, response code, headers,
 *   data and redirect status.
 */
function drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3) {
  global $db_prefix;

  $result = new stdClass();

  // Parse the URL and make sure we can handle the schema.
  $uri = parse_url($url);

  if ($uri == FALSE) {
    $result->error = 'unable to parse URL';
    $result->code = -1001;
    return $result;
  }

  if (!isset($uri['scheme'])) {
    $result->error = 'missing schema';
    $result->code = -1002;
    return $result;
  }

  switch ($uri['scheme']) {
    case 'http':
      $port = isset($uri['port']) ? $uri['port'] : 80;
      $host = $uri['host'] . ($port != 80 ? ':'. $port : '');
      $fp = @fsockopen($uri['host'], $port, $errno, $errstr, 15);
      break;
    case 'https':
      // Note: Only works for PHP 4.3 compiled with OpenSSL.
      $port = isset($uri['port']) ? $uri['port'] : 443;
      $host = $uri['host'] . ($port != 443 ? ':'. $port : '');
      $fp = @fsockopen('ssl://'. $uri['host'], $port, $errno, $errstr, 20);
      break;
    default:
      $result->error = 'invalid schema '. $uri['scheme'];
      $result->code = -1003;
      return $result;
  }

  // Make sure the socket opened properly.
  if (!$fp) {
    // When a network error occurs, we use a negative number so it does not
    // clash with the HTTP status codes.
    $result->code = -$errno;
    $result->error = trim($errstr);

    // Mark that this request failed. This will trigger a check of the web
    // server's ability to make outgoing HTTP requests the next time that
    // requirements checking is performed.
    // @see system_requirements()
    //variable_set('drupal_http_request_fails', TRUE);

    return $result;
  }

  // Construct the path to act on.
  $path = isset($uri['path']) ? $uri['path'] : '/';
  if (isset($uri['query'])) {
    $path .= '?'. $uri['query'];
  }

  // Create HTTP request.
  $defaults = array(
    // RFC 2616: "non-standard ports MUST, default ports MAY be included".
    // We don't add the port to prevent from breaking rewrite rules checking the
    // host that do not take into account the port number.
    'Host' => "Host: $host",
    'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)',
  );

  // Only add Content-Length if we actually have any content or if it is a POST
  // or PUT request. Some non-standard servers get confused by Content-Length in
  // at least HEAD/GET requests, and Squid always requires Content-Length in
  // POST/PUT requests.
  $content_length = strlen($data);
  if ($content_length > 0 || $method == 'POST' || $method == 'PUT') {
    $defaults['Content-Length'] = 'Content-Length: '. $content_length;
  }

  // If the server url has a user then attempt to use basic authentication
  if (isset($uri['user'])) {
    $defaults['Authorization'] = 'Authorization: Basic '. base64_encode($uri['user'] . (!empty($uri['pass']) ? ":". $uri['pass'] : ''));
  }

  // If the database prefix is being used by SimpleTest to run the tests in a copied
  // database then set the user-agent header to the database prefix so that any
  // calls to other Drupal pages will run the SimpleTest prefixed database. The
  // user-agent is used to ensure that multiple testing sessions running at the
  // same time won't interfere with each other as they would if the database
  // prefix were stored statically in a file or database variable.
  if (is_string($db_prefix) && preg_match("/^simpletest\d+$/", $db_prefix, $matches)) {
    $defaults['User-Agent'] = 'User-Agent: ' . $matches[0];
  }

  foreach ($headers as $header => $value) {
    $defaults[$header] = $header .': '. $value;
  }

  $request = $method .' '. $path ." HTTP/1.0\r\n";
  $request .= implode("\r\n", $defaults);
  $request .= "\r\n\r\n";
  $request .= $data;

  $result->request = $request;

  fwrite($fp, $request);

  // Fetch response.
  $response = '';
  while (!feof($fp) && $chunk = fread($fp, 1024)) {
    $response .= $chunk;
  }
  fclose($fp);

  // Parse response.
  list($split, $result->data) = explode("\r\n\r\n", $response, 2);
  $split = preg_split("/\r\n|\n|\r/", $split);

  list($protocol, $code, $text) = explode(' ', trim(array_shift($split)), 3);
  $result->headers = array();

  // Parse headers.
  while ($line = trim(array_shift($split))) {
    list($header, $value) = explode(':', $line, 2);
    if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
      // RFC 2109: the Set-Cookie response header comprises the token Set-
      // Cookie:, followed by a comma-separated list of one or more cookies.
      $result->headers[$header] .= ','. trim($value);
    }
    else {
      $result->headers[$header] = trim($value);
    }
  }

  $responses = array(
    100 => 'Continue', 101 => 'Switching Protocols',
    200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
    300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
    400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed',
    500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported'
  );
  // RFC 2616 states that all unknown HTTP codes must be treated the same as the
  // base code in their class.
  if (!isset($responses[$code])) {
    $code = floor($code / 100) * 100;
  }

  switch ($code) {
    case 200: // OK
    case 304: // Not modified
      break;
    case 301: // Moved permanently
    case 302: // Moved temporarily
    case 307: // Moved temporarily
      $location = $result->headers['Location'];

      if ($retry) {
        $result = drupal_http_request($result->headers['Location'], $headers, $method, $data, --$retry);
        $result->redirect_code = $result->code;
      }
      $result->redirect_url = $location;

      break;
    default:
      $result->error = $text;
  }

  $result->code = $code;
  return $result;
}

function amazon_item_clean_xml($xml) {
  $metadata = amazon_data_cache();
  $item = array();

  // Pull the absolute basic information Amazon keeps at the top level
  // of the XML tree, cast to string, and move on.
  $item['asin'] = (string)$xml->ASIN;
  $item['salesrank'] = (string)$xml->SalesRank;
  $item['detailpageurl'] = (string)$xml->DetailPageURL;

  if (!empty($xml->ItemAttributes->ListPrice)) {
    $item['listpriceamount']		 = (string)$xml->ItemAttributes->ListPrice->Amount;
    $item['listpricecurrencycode']	 = (string)$xml->ItemAttributes->ListPrice->CurrencyCode;
    $item['listpriceformattedprice'] = (string)$xml->ItemAttributes->ListPrice->FormattedPrice;
  }

  $participant_types = split(',', AMAZON_PARTICIPANT_TYPES);

  // Pull in the basics of the ItemAttributes collection.
  foreach((array)($xml->ItemAttributes) as $key => $value) {
    if (is_string($value) && !in_array($key, $participant_types)) {
      $key = strtolower($key);
      $item[$key] = $value;
    }
  }

  // Handle the Authors/Artists/Etc.
  foreach ($participant_types as $key) {
    if (isset($xml->ItemAttributes->$key)) {
      foreach($xml->ItemAttributes->$key as $value) {
        $item[strtolower($key)][] = (string)$value;
        $item['participants'][] = (string)$value;
      }
    }
  }

  // Handle the product images. In theory, there could be a million different
  // product image types. We're only going to check for the most common ones
  // and ignore the rest for now.
  $supported_sizes = split(',', AMAZON_IMAGE_SIZES);
  if (isset($xml->ImageSets->ImageSet)) {
    foreach((array)$xml->ImageSets->ImageSet as $key => $data) {
      if (in_array($key, $supported_sizes)) {
        $item['imagesets'][strtolower($key)] = array(
          'url' => (string)$data->URL,
          'height' => (string)$data->Height,
          'width' => (string)$data->Width,
        );
      }
    }
  }

  // Handle the editorial reviews.
  if (isset($xml->EditorialReviews)) {
    foreach($xml->EditorialReviews->EditorialReview as $data) {
      $item['editorialreviews'][] = array(
        'source' => (string)$data->Source,
        'content' => (string)$data->Content,
      );
    }
  }


  return $item;
}

function amazon_get_associate_id() {
  switch (variable_get('amazon_associate_setting', 'association')) {
    case 'association':
      return 'drupal0a-20';
      break;
    case 'custom':
      return variable_get('amazon_custom_associate_id', '');
      break;
    default:
      return FALSE;
  }
}
function amazon_default_locales() {
  $locales = array();
  $locales['US'] = array(
    'url'     => 'http://ecs.amazonaws.com/onca/xml',
    'name'    => ('United States'),
  );
  $locales['UK'] = array(
    'url'     => 'http://ecs.amazonaws.co.uk/onca/xml',
    'name'    => ('United Kingdom'),
  );
  $locales['JP'] = array(
    'url'     => 'http://ecs.amazonaws.jp/onca/xml',
    'name'    => ('Japan'),
  );
  $locales['FR'] = array(
    'url'     => 'http://ecs.amazonaws.fr/onca/xml',
    'name'    => ('France'),
  );
  $locales['DE'] = array(
    'url'     => 'http://ecs.amazonaws.de/onca/xml',
    'name'    => ('Germany'),
  );
  $locales['CA'] = array(
    'url'     => 'http://ecs.amazonaws.ca/onca/xml',
    'name'    => ('Canada'),
  );

  return $locales;
}
function amazon_data_cache($reset = FALSE) {
  static $data;
  if (!isset($data) || $reset) {
  	if (0) {
    //if (!$reset && ($cache = cache_get('amazon:metadata')) && !empty($cache->data)) {
      $data = $cache->data;
    } else {
      $data = array();
      $data['locales'] = amazon_default_locales();

//      drupal_alter('amazon_metadata', $data);
  //    cache_set('amazon:metadata', $data);
    }
  }
  return $data;
}

function variable_get($variable) {
	global $configuration;
	
	switch ($variable) {
		case 'amazon_aws_access_key' :		
			return $configuration['amazon_aws_access_key'];
		case 'amazon_aws_secret_access_key':
			return $configuration['amazon_aws_secret_access_key'];
		break;
	}	
}

function amazon_http_request($operation, $parameters = array(), $locale = NULL) {
  if (!isset($locale)) {
  //  $locale = variable_get('amazon_locale', 'US');
  }
  $locale ='US';  
  $metadata = amazon_data_cache();
  $locale_data = $metadata['locales'][$locale];

  // Populate the params with default data.
  $parameters += array(
    'Service' => 'AWSECommerceService',
    'Version' => AMAZON_ECS_SCHEMA,
    'AWSAccessKeyId' => variable_get('amazon_aws_access_key', ''),
    'Operation' => $operation,
  );

//  Your request should have atleast 1 of the following parameters: Keywords, Title, Power, BrowseNode, Artist, Author, Actor, Director, AudienceRating, Manufacturer, MusicLabel, Composer, Publisher, Brand, Conductor, Orchestra, TextStream, Cuisine, City, Neighborhood.
  
  if ($associate_id = amazon_get_associate_id()) {
    $parameters += array(
      'AssociateTag' => $associate_id,
    );
  }
  //var_dump($parameters);
  $parameters += array('Timestamp' => gmdate("Y-m-d\TH:i:s"). 'Z');
  uksort($parameters, 'strnatcmp');

  $params = array();
  foreach($parameters as $key => $value) {
    if (is_array($value)) {
      $value = implode(',', $value);
    }
    $param = str_replace("%7E", "~", rawurlencode($key));
    $value = str_replace("%7E", "~", rawurlencode($value));
    $params[] = $param .'='. $value;
  }
  $secret_access_key=variable_get('amazon_aws_secret_access_key',"");
  if ($secret_access_key == "") {
    watchdog('amazon',"No Secret Access Key configured. You must configure one at Admin->Settings->Amazon API",NULL,'error');
    
    return FALSE;
  }
  // Thanks for signature creation code from http://mierendo.com/software/aws_signed_query/
  $query_string = implode('&',$params);
  $parsed_url = parse_url($locale_data['url']);
  $host=strtolower($parsed_url['host']);
  $string_to_sign="GET\n$host\n{$parsed_url['path']}\n$query_string";

  $signature = base64_encode(hash_hmac('sha256', $string_to_sign, $secret_access_key, TRUE));
  $signature = str_replace("%7E", "~", rawurlencode($signature));
  $query_string .= "&Signature=$signature";

  $url = $locale_data['url'] .'?'. $query_string;

  // Make the request and return a SimpleXML object.
  $results = drupal_http_request($url, array(), 'GET');
  
  //var_dump($results);
  if ($results->code == 200) {
    $xml = new SimpleXMLElement($results->data);
    return $xml;
  }
  if ($results->code >= 400 && $results->code < 500) {
    try {
      $xml = new SimpleXMLElement($results->data);
    } catch (Exception $e) {
      watchdog('amazon', "Error handling results: http_code=%http_code, data=%data.",array('%http_code' => $results->code, '%data' => (string)$results->data) );
      return FALSE;
    }
    watchdog('amazon', "HTTP code %http_code accessing Amazon's AWS service: %code, %message", array('%http_code' => $results->code, '%code' => (string)$xml->Error->Code, '%message' => (string)$xml->Error->Message));
    return FALSE;
  }
  watchdog('amazon', "Error accessing Amazon AWS web service. HTTP result code=%code, error=%error", array('%code' => $results->code, '%error' => $results->error));
  return FALSE;
}

function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) {
	global $user, $base_root;
 	$log_message = array(
	    'type'        => $type,
	    'message'     => $message,
	    'variables'   => $variables,
	    'severity'    => $severity,
	    'link'        => $link,
	    'user'        => $user,
	    'request_uri' => $base_root . request_uri(),
	 //   'referer'     => referer_uri(),
	  //  'ip'          => ip_address(),
	    'timestamp'   => time(),
    );
	echo '<div style="padding:5px;font-size:12px; background-color:#f7f7ff;">';
	echo $message;
	echo '</div>';
}

function request_uri() {

  if (isset($_SERVER['REQUEST_URI'])) {
    $uri = $_SERVER['REQUEST_URI'];
  }
  else {
    if (isset($_SERVER['argv'])) {
      $uri = $_SERVER['SCRIPT_NAME'] .'?'. $_SERVER['argv'][0];
    }
    elseif (isset($_SERVER['QUERY_STRING'])) {
      $uri = $_SERVER['SCRIPT_NAME'] .'?'. $_SERVER['QUERY_STRING'];
    }
    else {
      $uri = $_SERVER['SCRIPT_NAME'];
    }
  }
  // Prevent multiple slashes to avoid cross site requests via the FAPI.
  $uri = '/'. ltrim($uri, '/');
  return $uri;
}

function amazon_search_simple_search($keywords = '', $parameters = array()) {
	$parameters += array(
		'ResponseGroup' => 'ItemAttributes,EditorialReview',
		'SearchIndex' => 'Blended',
  	);
  	$parameters['Keywords'] = urlencode($keywords);

	$items = array();
  	$results = amazon_http_request('ItemSearch', $parameters);
  	
	if (!empty($results)) {		
		foreach($results->Items->Item as $xml) {
    		$item = amazon_item_clean_xml($xml);
    		$items[$item['asin']] = $item;
		}
 	} 	
  	return $items;  	
}


function printSearchResults($parsed_xml, $SearchIndex){
	$numOfItems = $parsed_xml->Items->TotalResults;
	$totalPages = $parsed_xml->Items->TotalPages;
	$CartId = $_GET['CartId'];
	$HMAC = urlencode($_GET['HMAC']);
	print("<table>");
	if($numOfItems>0){
		foreach($parsed_xml->Items->Item as $current){
			if(isset($current->Offers->Offer->OfferListing->OfferListingId)){ //only show items for which there is an offer
				print("<tr><td><img src='".$current->MediumImage->URL."'></td>");
				print("<td><font size='-1'><b>".$current->ItemAttributes->Title."</b>");
				if(isset($current->ItemAttributes->Director)){
					print("<br>Director: ".$current->ItemAttributes->Director);
				} elseif(isset($current->ItemAttributes->Author)) {
					print("<br>Author: ".$current->ItemAttributes->Author);
				} elseif(isset($current->ItemAttributes->Artist)) {
					print("<br>Artist: ".$current->ItemAttributes->Artist);
				}
				print("<br>Price: ".$current->Offers->Offer->OfferListing->Price->FormattedPrice);
				$asin = $current->ASIN;
				$details = "SimpleStore.php?Action=SeeDetails&ASIN=$asin&SearchIndex=$SearchIndex&CartId=$CartId&HMAC=$HMAC";
				print("<br><a href=$details>See Details</a>");
				$offerListingId = urlencode($current->Offers->Offer->OfferListing->OfferListingId);
				$CartAdd = "SimpleStore.php?Action=CartAdd&OfferListingId=$offerListingId&CartId=$CartId&HMAC=$HMAC";
				print("&nbsp;&nbsp;&nbsp; <a href=$CartAdd>Add to Cart</a>");
				print("<tr><td colspan=2>&nbsp;</td> </tr> ");
			}
		}
	}else{
		print("<center>No matches found.</center>");
	}
	print("<tr><td align='left'>");
	//allow for paging through results
	if($_GET['ItemPage'] > 1 && $totalPages > 1){ //check to see if there are previous pages
		$Keywords = urlencode($_GET['Keywords']);
		$ItemPage = $_GET['ItemPage']-1;
		$prevPage = "SimpleStore.php?Action=Search&SearchIndex=$SearchIndex&Keywords=$Keywords&ItemPage=$ItemPage&CartId=$CartId&HMAC=$HMAC";
		print("<a href=$prevPage>Previous Page</a></td><td align='right'>");
	}
	if($_GET['ItemPage'] < $totalPages){ //check to see if there are more pages
		$Keywords = urlencode($_GET['Keywords']);
		$ItemPage = $_GET['ItemPage']+1;
		$nextPage = "SimpleStore.php?Action=Search&SearchIndex=$SearchIndex&Keywords=$Keywords&ItemPage=$ItemPage&CartId=$CartId&HMAC=$HMAC";
		print("<a href=$nextPage>Next Page</a></td></tr>");
	}
}

function ItemLookup($asin, $SearchIndex){
	$request = "http://ecs.amazonaws.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=".KEYID."&AssociateTag=".AssocTag."&Version=2006-09-11&Operation=ItemLookup&ItemId=$asin&ResponseGroup=Medium,Offers";
	//The use of `file_get_contents` may not work on all servers because it relies on the ability to open remote URLs using the file manipulation functions. 
	//PHP gives you the ability to disable this functionality in your php.ini file and many administrators do so for security reasons.
	//If your administrator has not done so, you can comment out the following 5 lines of code and uncomment the 6th.  
	$session = curl_init($request);
	curl_setopt($session, CURLOPT_HEADER, false);
	curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
	$response = curl_exec($session);
	curl_close($session);
	//$response = file_get_contents($request);
	$parsed_xml = simplexml_load_string($response);
	printDetails($parsed_xml, $SearchIndex);
}


?>